package me.zd.tool.hardlink.view;

import me.zd.tool.hardlink.service.I18nService;
import me.zd.tool.hardlink.view.i18n.I18nAdapterFactory;
import me.zd.tool.hardlink.view.i18n.I18nResetLabelAdapter;

import javax.swing.*;
import javax.swing.filechooser.FileFilter;
import java.awt.*;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.util.ArrayList;
import java.util.List;

public class MainFrame extends JFrame {

    private final DateTimeFormatter normalizeDateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private final DateTimeFormatter[] tryNormalizeDTFAry = {
            DateTimeFormatter.ofPattern("yyyy-M-d H:m:s"),
            DateTimeFormatter.ofPattern("yyyy-M-d H:m"),
            DateTimeFormatter.ofPattern("yyyy-M-d H"),
            DateTimeFormatter.ofPattern("yyyy-M-d"),
    };
    private final List<I18nAdapter> i18nList = new ArrayList<>();
    public final JTextField tfFromPath;
    public final JTextField tfToPath;
    public final JTextField tfIncludeSuffix;
    public final JTextField tfExcludeSuffix;
    public final JTextField tfTargetFilePrefix;
    public final JTextField tfFromPattern;
    public final JTextField tfToPattern;
    public final JButton buttonStart;
    public final I18nAdapter buttonStartI18n;
    public final JTextArea taConsole;
    public final JSpinner spScanDepth;
    public final JComboBox<PairItem> cBoxSelectLanguage;
    public final JSpinner spRegexpWithParentLevel;
    public final JButton buttonImport;
    public final JButton buttonExport;
    public final JButton buttonReset;
    public final I18nResetLabelAdapter labelResetI18n;
    public final JButton buttonTest;
    public final I18nAdapter buttonTestI18n;
    public final JCheckBox cbUsingRegexp;
    public final JCheckBox cbUsingRegexpReplacement;
    public final JCheckBox cbUsingRegexpWithParentLevel;
    public final JLabel labelRegexpWithParentLabelWarning;
    public final JButton buttonAutoFillPattern;
    public final JCheckBox cbUsingModifiedTime;
    public final JTextField tfBeginModifiedTime;
    public final JTextField tfEndModifiedTime;

    public MainFrame() {
        setDefaultLookAndFeelDecorated(true);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setMinimumSize(new Dimension(420, 340));
        setSize(640, 400);
        usingI18n(this, "title");
        Font font = new Font(Font.MONOSPACED, Font.PLAIN, 12);
        UIManager.put("TextField.font", font);
        UIManager.put("TextArea.font", font);

        // 页签
        JTabbedPane tabbedPane = new JTabbedPane();
        add(tabbedPane, BorderLayout.CENTER);

        // ===主面板===
        JPanel mainPanel = new JPanel();
        tabbedPane.addTab("tab.main.title", null, mainPanel, "tab.main.tips");
        mainPanel.setLayout(new GridBagLayout());

        // 来源路径
        JLabel fromPathLabel = new JLabel();
        usingI18n(fromPathLabel, "label.from.path");
        mainPanel.add(fromPathLabel, createLeftGrid());
        tfFromPath = new JTextField();
        JButton buttonFromPath = new JButton();
        usingI18n(buttonFromPath, "button.select.path");
        JButton buttonToFromOpenExplore = new JButton();
        usingI18n(buttonToFromOpenExplore, "button.open.explore");
        mainPanel.add(createPathPanel(tfFromPath, buttonFromPath, buttonToFromOpenExplore, "dialog.choose.from.path"), createRightGrid());

        // 目标路径
        JLabel toPathLabel = new JLabel();
        usingI18n(toPathLabel, "label.to.path");
        mainPanel.add(toPathLabel, createLeftGrid());
        tfToPath = new JTextField();
        JButton buttonToPath = new JButton();
        usingI18n(buttonToPath, "button.select.path");
        JButton buttonToPathOpenExplore = new JButton();
        usingI18n(buttonToPathOpenExplore, "button.open.explore");
        mainPanel.add(createPathPanel(tfToPath, buttonToPath, buttonToPathOpenExplore, "dialog.choose.to.path"), createRightGrid());

        // 包含后缀
        JLabel includeSuffixLabel = new JLabel();
        usingI18n(includeSuffixLabel, "label.include.suffix");
        mainPanel.add(includeSuffixLabel, createLeftGrid());
        tfIncludeSuffix = new JTextField();
        mainPanel.add(tfIncludeSuffix, createRightGrid());

        // 排除后缀
        JLabel excludeSuffixLabel = new JLabel();
        usingI18n(excludeSuffixLabel, "label.exclude.suffix");
        mainPanel.add(excludeSuffixLabel, createLeftGrid());
        tfExcludeSuffix = new JTextField();
        mainPanel.add(tfExcludeSuffix, createRightGrid());

        // 开始按钮
        buttonStart = new JButton();
        buttonStartI18n = usingI18n(buttonStart, "button.start", "button.processing");
        Font startFont = new Font(Font.DIALOG, Font.BOLD, 16);
        buttonStart.setFont(startFont);
        GridBagConstraints startGBC = createWholeGrid();
        startGBC.insets = new Insets(5, 10, 5, 10);
        mainPanel.add(buttonStart, startGBC);

        // 信息面板
        taConsole = new JTextArea();
        taConsole.setEditable(false);
        JScrollPane scrollPane = new JScrollPane(taConsole);
        scrollPane.setBorder(BorderFactory.createLineBorder(Color.GRAY));
        GridBagConstraints consoleGBC = createWholeGrid();
        consoleGBC.fill = GridBagConstraints.BOTH;
        consoleGBC.weighty = 1.0;
        mainPanel.add(scrollPane, consoleGBC);

        // ===高级设定===
        JPanel advancePanel = new JPanel();
        advancePanel.setBorder(BorderFactory.createLineBorder(Color.GRAY));
        tabbedPane.addTab("tab.advance.title", null, advancePanel, "tab.advance.tips");
        advancePanel.setLayout(new GridBagLayout());

        // 设置导入导出
        JLabel settingOperationLabel = new JLabel();
        usingI18n(settingOperationLabel, "label.setting.operation");
        advancePanel.add(settingOperationLabel, createLeftGrid());
        JPanel settingOperationPanel = new JPanel();
        settingOperationPanel.setLayout(new GridBagLayout());
        buttonImport = new JButton();
        usingI18n(buttonImport, "button.setting.import");
        settingOperationPanel.add(buttonImport, createFlowButtonGrid());
        buttonExport = new JButton();
        usingI18n(buttonExport, "button.setting.export");
        settingOperationPanel.add(buttonExport, createFlowButtonGrid());
        buttonReset = new JButton();
        usingI18n(buttonReset, "button.setting.reset");
        GridBagConstraints resetButtonGBC = createFlowButtonGrid();
        resetButtonGBC.insets = new Insets(1, 5, 1, 1);
        settingOperationPanel.add(buttonReset, resetButtonGBC);
        JLabel resetLabel = new JLabel();
        resetLabel.setForeground(Color.RED);
        labelResetI18n = new I18nResetLabelAdapter(resetLabel);
        i18nList.add(labelResetI18n);
        GridBagConstraints resetLabelGBC = createFlowButtonGrid();
        resetLabelGBC.insets = new Insets(1, 5, 1, 1);
        settingOperationPanel.add(resetLabel, resetLabelGBC);
        GridBagConstraints settingButtonGBC = createRightGrid();
        settingButtonGBC.fill = GridBagConstraints.NONE;
        advancePanel.add(settingOperationPanel, settingButtonGBC);

        // 插入空白
        JPanel upperEmptyPanel = new JPanel();
        upperEmptyPanel.setSize(10, 10);
        advancePanel.add(upperEmptyPanel, createWholeGrid());

        // 语言选择
        JLabel selectLanguageLabel = new JLabel();
        usingI18n(selectLanguageLabel, "label.select.language");
        advancePanel.add(selectLanguageLabel, createLeftGrid());
        List<PairItem> languageItems = new ArrayList<>();
        for (int i = 0; i < I18nService.INS.ALL_LANGUAGE_ARY.length; i++) {
            String value = I18nService.INS.ALL_LANGUAGE_ARY[i];
            languageItems.add(new PairItem(I18nService.INS.getSpecifyLanguage(value, "language.name"), value));
        }
        cBoxSelectLanguage = new JComboBox<>(languageItems.toArray(new PairItem[0]));
        GridBagConstraints selectLanguageGBC = createRightGrid();
        selectLanguageGBC.fill = GridBagConstraints.NONE;
        advancePanel.add(cBoxSelectLanguage, selectLanguageGBC);

        // 搜索深度
        JLabel scanDepthLabel = new JLabel();
        usingI18n(scanDepthLabel, "label.scan.depth");
        advancePanel.add(scanDepthLabel, createLeftGrid());
        spScanDepth = new JSpinner(new SpinnerNumberModel(1, 1, 99, 1));
        GridBagConstraints scanDepthGBC = createRightGrid();
        scanDepthGBC.fill = GridBagConstraints.NONE;
        advancePanel.add(spScanDepth, scanDepthGBC);

        // 目标文件追加前缀
        JLabel targetFilePrefixLabel = new JLabel();
        usingI18n(targetFilePrefixLabel, "label.target.file.prefix");
        advancePanel.add(targetFilePrefixLabel, createLeftGrid());
        tfTargetFilePrefix = new JTextField();
        advancePanel.add(tfTargetFilePrefix, createRightGrid());

        // 插入空白
        JPanel middleEmptyPanel = new JPanel();
        middleEmptyPanel.setSize(10, 2);
        advancePanel.add(middleEmptyPanel, createWholeGrid());

        // 修改时间
        cbUsingModifiedTime = new JCheckBox();
        cbUsingModifiedTime.addChangeListener(e -> updateModifiedTimeView());
        usingI18n(cbUsingModifiedTime, "check.box.modified.time");
        GridBagConstraints modifiedTimeGBC = createLeftGrid();
        modifiedTimeGBC.anchor = GridBagConstraints.NORTHWEST;
        advancePanel.add(cbUsingModifiedTime, modifiedTimeGBC);
        JPanel modifiedTimePanel = new JPanel();
        modifiedTimePanel.setLayout(new GridBagLayout());
        JLabel beginModifiedLabel = new JLabel();
        usingI18n(beginModifiedLabel, "label.modified.time.from");
        tfBeginModifiedTime = new JTextField();
        createModifiedTime(modifiedTimePanel, beginModifiedLabel, tfBeginModifiedTime);
        JLabel endModifiedLabel = new JLabel();
        usingI18n(endModifiedLabel, "label.modified.time.to");
        tfEndModifiedTime = new JTextField();
        createModifiedTime(modifiedTimePanel, endModifiedLabel, tfEndModifiedTime);
        advancePanel.add(modifiedTimePanel, createRightGrid());

        // 使用正则表达式匹配
        cbUsingRegexp = new JCheckBox();
        cbUsingRegexp.addChangeListener(e -> updateRegexpView());
        usingI18n(cbUsingRegexp, "check.box.using.regexp.matching");
        advancePanel.add(cbUsingRegexp, createLeftGridWest());
        tfFromPattern = new JTextField();
        buttonAutoFillPattern = new JButton();
        usingI18n(buttonAutoFillPattern, "button.auto.fill.pattern");
        JPanel fromPatternPanel = new JPanel();
        fromPatternPanel.setLayout(new GridBagLayout());
        fromPatternPanel.add(tfFromPattern, createPathLeftGrid());
        fromPatternPanel.add(buttonAutoFillPattern, createFlowButtonGrid());
        advancePanel.add(fromPatternPanel, createRightGrid());

        // 来源正则表达式包含父级目录层数
        cbUsingRegexpWithParentLevel = new JCheckBox();
        cbUsingRegexpWithParentLevel.addChangeListener(e -> updateRegexpView());
        usingI18n(cbUsingRegexpWithParentLevel, "check.box.regexp.with.parent.level");
        advancePanel.add(cbUsingRegexpWithParentLevel, createLeftGridWest());
        JPanel regexpWithParentLevelPanel = new JPanel();
        regexpWithParentLevelPanel.setLayout(new GridBagLayout());
        spRegexpWithParentLevel = new JSpinner(new SpinnerNumberModel(1, 1, 99, 1));
        GridBagConstraints spRegexpWithParentLevelGBC = createLeftGrid();
        spRegexpWithParentLevelGBC.insets = new Insets(0,0,0,5);
        regexpWithParentLevelPanel.add(spRegexpWithParentLevel, spRegexpWithParentLevelGBC);
        labelRegexpWithParentLabelWarning = new JLabel();
        labelRegexpWithParentLabelWarning.setForeground(Color.RED);
        usingI18n(labelRegexpWithParentLabelWarning, "warning.regexp.with.parent.level");
        regexpWithParentLevelPanel.add(labelRegexpWithParentLabelWarning, createRightGrid());
        GridBagConstraints regexpWithParentLevelPanelGBC = createRightGrid();
        regexpWithParentLevelPanelGBC.fill = GridBagConstraints.NONE;
        advancePanel.add(regexpWithParentLevelPanel, regexpWithParentLevelPanelGBC);

        // 目标正则替换
        cbUsingRegexpReplacement = new JCheckBox();
        cbUsingRegexpReplacement.addChangeListener(e -> updateRegexpView());
        usingI18n(cbUsingRegexpReplacement, "check.box.using.regexp.replacement");
        advancePanel.add(cbUsingRegexpReplacement, createLeftGridWest());
        tfToPattern = new JTextField();
        advancePanel.add(tfToPattern, createRightGrid());

        // 测试
        buttonTest = new JButton();
        buttonTestI18n = usingI18n(buttonTest, "button.test.begin", "button.test.testing");
        GridBagConstraints testGBC = createWholeGrid();
        testGBC.insets = new Insets(10, 50, 0, 50);
        testGBC.anchor = GridBagConstraints.CENTER;
        advancePanel.add(buttonTest, testGBC);

        // 插入空白
        JPanel footerEmptyPanel = new JPanel();
        GridBagConstraints footerGBC = createWholeGrid();
        footerGBC.fill = GridBagConstraints.BOTH;
        footerGBC.weighty = 1.0;
        advancePanel.add(footerEmptyPanel, footerGBC);

        // 标签页i18n
        usingI18n(tabbedPane);

        // 更新页面内关联
        updateRegexpView();
        updateModifiedTimeView();
    }

    private I18nAdapter usingI18n(Object component, String... i18nKey) {
        I18nAdapter i18nAdapter = I18nAdapterFactory.createAdapter(component, i18nKey);
        if (i18nAdapter != null) {
            i18nList.add(i18nAdapter);
        }
        return i18nAdapter;
    }

    private GridBagConstraints createWholeGrid() {
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = 1;
        gbc.gridy = GridBagConstraints.RELATIVE;
        gbc.gridwidth = 2;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.weightx = 1.0;
        gbc.insets = new Insets(1, 1, 1, 1);
        return gbc;
    }

    private GridBagConstraints createLeftGrid() {
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = 1;
        gbc.gridy = GridBagConstraints.RELATIVE;
        gbc.anchor = GridBagConstraints.EAST;
        gbc.insets = new Insets(2, 5, 2, 5);
        return gbc;
    }

    private GridBagConstraints createLeftGridWest() {
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = 1;
        gbc.gridy = GridBagConstraints.RELATIVE;
        gbc.anchor = GridBagConstraints.WEST;
        gbc.insets = new Insets(2, 5, 2, 5);
        return gbc;
    }

    private GridBagConstraints createRightGrid() {
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = 2;
        gbc.gridy = GridBagConstraints.RELATIVE;
        gbc.weightx = 1.0;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.anchor = GridBagConstraints.WEST;
        gbc.insets = new Insets(1, 1, 1, 1);
        return gbc;
    }

    private JPanel createPathPanel(JTextField tf, JButton button, JButton buttonOpenExplore, String dialogTitleKey) {
        JPanel panel = new JPanel();
        panel.setLayout(new GridBagLayout());
        panel.add(tf, createPathLeftGrid());
        panel.add(button, createFlowButtonGrid());
        panel.add(buttonOpenExplore, createFlowButtonGrid());
        button.addActionListener(e -> {
            JFileChooser fileChooser = new JFileChooser(tf.getText());
            fileChooser.setDialogTitle(I18nService.INS.get(dialogTitleKey));
            fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
            Action details = fileChooser.getActionMap().get("viewTypeDetails");
            details.actionPerformed(null);
            int option = fileChooser.showOpenDialog(this);
            if (option == JFileChooser.APPROVE_OPTION) {
                File file = fileChooser.getSelectedFile();
                tf.setText(file.getAbsolutePath());
            }
        });
        buttonOpenExplore.addActionListener(e -> {
            String path = tf.getText();
            if (path == null || path.trim().length() == 0) {
                appendConsole(I18nService.INS.format("open.explore.not.dictionary", ""));
                return;
            }
            File file = new File(path);
            if (file.isDirectory()) {
                Desktop desktop = Desktop.getDesktop();
                try {
                    desktop.open(file);
                } catch (IOException ex) {
                    appendThrowable(ex);
                }
            } else {
                appendConsole(I18nService.INS.format("open.explore.not.dictionary", file.getAbsolutePath()));
            }
        });
        return panel;
    }

    private GridBagConstraints createPathLeftGrid() {
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = 1;
        gbc.gridy = 1;
        gbc.weightx = 1.0;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.insets = new Insets(0, 0, 0, 1);
        return gbc;
    }

    private GridBagConstraints createFlowButtonGrid() {
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = GridBagConstraints.RELATIVE;
        gbc.gridy = 1;
        gbc.insets = new Insets(0, 0, 0, 2);
        return gbc;
    }

    private void createModifiedTime(JPanel panel, JLabel label, JTextField tf) {
        panel.add(label, createModifiedTimeLeftGrid());
        panel.add(tf, createModifiedTimeTextGrid());

        tf.addFocusListener(new FocusListener() {
            @Override
            public void focusGained(FocusEvent e) {
                tf.setForeground(Color.BLACK);
            }

            @Override
            public void focusLost(FocusEvent e) {
                String t = normalizeDate(tf.getText());
                if (t != null) {
                    tf.setText(t);
                } else {
                    tf.setForeground(Color.RED);
                }
            }
        });
    }

    private GridBagConstraints createModifiedTimeLeftGrid() {
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = 1;
        gbc.gridy = GridBagConstraints.RELATIVE;
        gbc.insets = new Insets(0, 0, 0, 2);
        return gbc;
    }

    private GridBagConstraints createModifiedTimeTextGrid() {
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = 2;
        gbc.gridy = GridBagConstraints.RELATIVE;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.weightx = 1;
        gbc.insets = new Insets(0, 0, 0, 2);
        return gbc;
    }

    private void updateRegexpView() {
        if (cbUsingRegexp.isSelected()) {
            tfFromPattern.setEnabled(true);
            tfFromPattern.setEditable(true);
            buttonAutoFillPattern.setEnabled(true);
            cbUsingRegexpReplacement.setEnabled(true);
            tfToPattern.setEnabled(cbUsingRegexpReplacement.isSelected());
            tfToPattern.setEditable(cbUsingRegexpReplacement.isSelected());
            cbUsingRegexpWithParentLevel.setEnabled(true);
            spRegexpWithParentLevel.setEnabled(cbUsingRegexpWithParentLevel.isSelected());
            labelRegexpWithParentLabelWarning.setVisible(cbUsingRegexpWithParentLevel.isSelected()
                    && cbUsingRegexpReplacement.isSelected());
        } else {
            tfFromPattern.setEnabled(false);
            tfFromPattern.setEditable(false);
            cbUsingRegexpReplacement.setEnabled(false);
            tfToPattern.setEnabled(false);
            tfToPattern.setEditable(false);
            cbUsingRegexpWithParentLevel.setEnabled(false);
            spRegexpWithParentLevel.setEnabled(false);
            labelRegexpWithParentLabelWarning.setVisible(false);
            buttonAutoFillPattern.setEnabled(false);
        }
    }

    private void updateModifiedTimeView() {
        if (cbUsingModifiedTime.isSelected()) {
            tfBeginModifiedTime.setEditable(true);
            tfBeginModifiedTime.setEnabled(true);
            tfEndModifiedTime.setEditable(true);
            tfEndModifiedTime.setEnabled(true);
        } else {
            tfBeginModifiedTime.setEditable(false);
            tfBeginModifiedTime.setEnabled(false);
            tfEndModifiedTime.setEditable(false);
            tfEndModifiedTime.setEnabled(false);
        }
    }

    public void refreshAllText() {
        SwingUtilities.invokeLater(() -> {
            for (I18nAdapter component : i18nList) {
                component.updateI18n();
            }
        });
    }

    public void cleanConsole() {
        SwingUtilities.invokeLater(() -> taConsole.setText(""));
    }

    public void appendConsole(String text) {
        SwingUtilities.invokeLater(() -> taConsole.append(text + "\n"));
    }

    public String appendThrowable(Throwable t) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        t.printStackTrace(pw);
        String str = sw.toString();
        pw.close();
        appendConsole(str);
        return str;
    }

    public File popFileChooserForFile(String title, boolean save, String currentDictionaryPath, FileFilter fileFilter) {
        JFileChooser fileChooser = new JFileChooser(currentDictionaryPath != null ? currentDictionaryPath : ".");
        fileChooser.setDialogTitle(title);
        fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
        fileChooser.setDialogType(save ? JFileChooser.SAVE_DIALOG : JFileChooser.OPEN_DIALOG);
        fileChooser.setFileFilter(fileFilter);
        Action details = fileChooser.getActionMap().get("viewTypeDetails");
        details.actionPerformed(null);
        int option = fileChooser.showDialog(this, null);
        if (option == JFileChooser.APPROVE_OPTION) {
            return fileChooser.getSelectedFile();
        } else {
            return null;
        }
    }

    public void popMessageDialog(String title, String text) {
        JOptionPane.showMessageDialog(this, text, title, JOptionPane.INFORMATION_MESSAGE);
    }


    public String normalizeDate(String text) {
        if (text == null || text.isEmpty()) {
            return null;
        }
        try {
            normalizeDateTimeFormatter.parse(text);
            return text;
        } catch (DateTimeParseException e) {
            // nothing
        }
        text = text.replaceAll("：", ":").replaceAll("\\s+", " ")
                .replaceAll("[^:\\-\\s\\d]+", "-").trim();
        for (int i = 0; i < tryNormalizeDTFAry.length; i++) {
            DateTimeFormatter formatter = tryNormalizeDTFAry[i];
            try {
                TemporalAccessor dateTime = formatter.parse(text);
                if (!dateTime.isSupported(ChronoField.HOUR_OF_DAY)) {
                    dateTime = LocalDateTime.of(LocalDate.from(dateTime), LocalTime.of(0,0));
                }
                return normalizeDateTimeFormatter.format(dateTime);
            } catch (DateTimeParseException e) {
                // nothing
            }
        }

        return null;
    }
}
